'''

  pyQuery  (functional style of code)
  ,,,,,,,  ,,,,,,,,,,,,,,,,,,,,,,,,,,


 @author: Yauhen Yakimovich
 @copyright: Copyright (c) 2009 Yauhen Yakimovich
 @license: New FreeBSD license.
 @contact: lt;eugeny;dot;yakimovitch;at;gmail;dot;com;gt
 @version: 0.1.0
____________________________________________________________________________
                        .. because one should never underestimate simplicity
                                                                
                                                                A Foreword

Suppose I have

 foo = {'foo': 'bar', 'mess1' : 'gabarge1', 'mess2' : 'rest'}

and
 def cut_dict(k, d): d.pop(k,None); return d
 def set_dict(k, v, d):
    if k in d: d[k] = v ; return d

and I want to remove mess-like keys in a functional like manner. The order of 
my invocation, of course, will be prefix, like this

 print set_dict('foz','baz',cut_dict('mess2',cut_dict('mess1', foo)))

Now, further talk is not the python way of doing things. So if you are concerned 
of this, stop reading this now and forget all the things.

For those who is familiar with jQuery library on javascript, 
things a getting somewhere and thus make sense. We will change the order,
and obtain query style left-to-right functional behavior

 print q(foo).cut_dict('mess1').cut_dict('mess2').set_dict('foz','baz').r

'''
import inspect, string

class pyQuery(object):
    def __init__(self, arg=None):
        self.arg = arg # constructor's argument
        self.r = None
        self.__token = None
        self.__callnum = 0
        self.s = self # self for magic like slice etc
        self.__allowed_methods = ['assert_', 'check', 'load']
        self.__allowed_properties = ['r','s']        
    def assert_(self, expr):
        pass
    def check(self, expr): # if condition flow
        return True
    def load(self, name): #import
        pass
    #def __setattr__(self, name, value):
    #    pass

    def __getattribute__(self, name):

        # work with stack frames to obtain source of invocation
        frames = inspect.stack()
        frame = frames[1][0]

        try:
            invoking_self = None if not frame.f_locals.has_key('self') else frame.f_locals['self']

            # access private, reject externals
            if name[0:8] == '_pyQuery' or name[0:2] == '__':
                if self != invoking_self:
                    raise AttributeError('No, there is no luck with private attributes')
                else:
                    return super(pyQuery, self).__getattribute__(name)

            # shortcuts
            ga = super(pyQuery, self).__getattribute__
            allowed_methods = ga('_pyQuery__allowed_methods')
            allowed_properties = ga('_pyQuery__allowed_properties')

            # map local
            has_attr = True
            try:
                attr = ga(name)
            except AttributeError, e:
                has_attr = False

            if has_attr: #hasattr(super(pyQuery, self), name):                
                if hasattr(attr, '__call__') and name in allowed_methods:
                    return self.__proxy(attr)
                elif name in allowed_properties:
                    return attr
                else:
                    raise AttributeError('No luck with %s' % name)
            # map functions from frame
            elif name[0:2] == 'f_':
                look = 'function'
                name = name[2:]
            # check against string module
            elif name[0:2] == 's_':
                name = name[2:]
                return self.__s(name)                
            else:
                raise AttributeError('Unknown statement %s' % name)

            if frame.f_locals.has_key(name):
                attr = frame.f_locals[name]

                if hasattr(attr, '__call__'):
                    return self.__proxy(attr)
            if look == 'function':
                raise AttributeError('No, there is no such function called \'%s\'' % name)
            raise AttributeError('No, there is still no luck with statement %s' % name)

        finally:
            del frame
            del frames

    def __proxy(self, func):
        '''A proxy to cast a fairy-magic-query'''
        def wrap(*args, **kwds):
            #print dir(func.func_code.co_flags)            
            if self.__token is not None:
                if  type(args).__name__ == 'tuple':
                    args = list(args)
                acount = func.func_code.co_argcount
                skip = 1 if len(args) == acount else 0
                args = [self.__token] + args[skip:] # replace token as first argument
            self.r = func(*args, **kwds)
            self.__update_token_r()
            self.__callnum += 1
            return self
        return wrap

    def __call__(self, token):
        self.__token = token
        return self

    def __update_token_r(self):
        if self.r is not None:
            # if function does not return anything 
            # just pass the previous token around
            self.__token = self.r
        else:
            # assume that token is passed as obj by reference, so it is our true result
            self.r = self.__token

    # slice
    def __getslice__(self, left, right):
        if self.__callnum == 0:
            self.r = self.__token        
        self.r = self.r[left:right]
        self.__update_token_r()
        return self
        
    # --string--
    def __s(self, name):
        func = getattr(string, name, None)
        if func is None:
            raise Exception('%s does not exists', name)
        if not hasattr(func, '__call__'):
            raise Exception('%s is not callable', name)
        def wrap(*args, **kwds):
            newargs = list(args)
            newargs.insert(0,self.__token)            
            self.r = func(*newargs, **kwds)
            self.__update_token_r()
            return self
        return wrap

#===============================================================================
#  instantiate object
#===============================================================================

q = pyQuery()


#===============================================================================
#  module magic
#===============================================================================
__license__ = '''
Copyright (c) 2009, Yauhen Yakimovich
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its contributors 
      may be used to endorse or promote products derived from this software 
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
'''
